package al.xc.liveapi.aop;

import al.xc.common.exception.RequestDecodeException;
import al.xc.common.exception.ResultCodeDefine;
import al.xc.common.util.GsonUtils;
import al.xc.common.util.SingleUtils;
import al.xc.redis.RedisConnectManager;
import al.xc.redis.RedisLock;
import al.xc.springboot.aop.IReCallVerifier;
import al.xc.springboot.aop.RecallValidate;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 重复调用验证
 */
@Component
@Scope("singleton")
@Slf4j
public class RecallVerifierImpl implements IReCallVerifier {

    private RedisLock lock = new RedisLock(SingleUtils.instance(RedisConnectManager.class).connect());

    /**
     * 标识
     */
    private static final Map<String, String> identifs = new HashMap<>();

    /**
     * 开始
     *
     * @param joinPoint
     * @throws Exception
     */
    @Override
    public void before(JoinPoint joinPoint) throws Exception {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        RecallValidate recallValidate = method.getAnnotation(RecallValidate.class);

        String md5 = getParamsMD5(method, joinPoint.getArgs(), recallValidate);
        String ident = UUID.randomUUID().toString();

        boolean result = lock.trylock("RECALL_LOCK:" + method.getName() + ":" + md5, ident, recallValidate.seconds());
        if (!result) {
            throw new RequestDecodeException(ResultCodeDefine.ERROR_RECALL, "请勿重复请求");
        } else {
            synchronized (identifs) {
                identifs.put(md5, ident);
            }
        }
    }

    /**
     * 结束
     *
     * @param joinPoint
     * @throws Exception
     */
    @Override
    public void after(JoinPoint joinPoint) throws Exception {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        RecallValidate recallValidate = method.getAnnotation(RecallValidate.class);

        String md5 = getParamsMD5(method, joinPoint.getArgs(), recallValidate);

        String ident = null;
        synchronized (identifs) {
            ident = identifs.remove(md5);
        }
        if (null == ident) {
            return;
        }
        lock.unlock("RECALL_LOCK:" + method.getName() + ":" + md5, ident);
    }

    /**
     * 获取参数的md5值
     * @param method
     * @param args
     * @param validate
     * @return
     */
    private String getParamsMD5(Method method, Object[] args, RecallValidate validate) {
        String orgString = "";
        int[] keyindex = validate.keyindexs();
        for (int i = 0; i < keyindex.length; ++i) {
            if (keyindex[i] >= 0 && keyindex[i] < args.length) {
                orgString += GsonUtils.toJson(args[keyindex[i]]);
            }
        }
        return DigestUtils.md5DigestAsHex(orgString.getBytes());
    }
}
